/*
 * This file is part of Annuaire.
 *
 * Annuaire is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Annuaire is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Annuaire.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.miage.annuaire.data;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.miage.annuaire.AnnuaireUtil;
import org.miage.annuaire.Membre;
import org.miage.annuaire.Structure;

/**
 * Implémentation par défaut de l'interface ObjectDataAccess.
 * @author guhumber
 */
public class ObjectDataAccessImpl implements ObjectDataAccess {

	/**
	 * Renvoie la liste de toutes les structures de l'annuaire. La liste
	 * renvoyée contient les structures du premier niveau. Pour accéder aux
	 * niveaux inférieurs, il faut appeler la méthode getChilds() d'une
	 * structure.
	 * @return La liste des structures du premier niveau.
	 */
	@Override
	public List<Structure> getAllStructures() {

		// Récupération des clefs des structures du premier niveau.
		ArrayList<String> keys = this.getDataAccess().getStructures();
		
		// La liste des structures que l'on va renvoyer.
		List<Structure> structures = new LinkedList<Structure>();
		
		// Pour chaque structure du premier niveau, on remplit toutes ses
		// informations (nom court, nom long, membres, ...).
		for (String key : keys) {
			structures.add(this.getStructureByKey(key));
		}

		// Pour chaque structure du premier niveau, on construit toute sa
		// filiation récursivement.
		for (Structure structure : structures) {
			this.getChilds(structure);
		}
		
		// Fin.
		return structures;
	}
	
	/**
	 * Construit un objet structure à partir de sa clef.
	 * @param key Une clef de structure.
	 * @return Un objet structure construit. On a pas les informations sur la
	 * structure père ni sur les filles.
	 */
	@Override
	public Structure getStructureByKey(String key) {
		
		// Construction des informations basiques de la structure: clef, nom
		// court et nom long.
		Structure structure = new Structure();
		structure.setKey(key);
		structure.setShortName(this.getDataAccess().getShortName(key));
		structure.setCompleteName(this.getDataAccess().getCompleteName(key));
		
		this.fillLeader(structure);
		this.fillContact(structure);
		this.fillAssistants(structure);
		this.fillMembres(structure);
		
		return structure;
	}
	
	/**
	 * Remplit toutes les filles récursivement d'une structure.
	 * @param structure La structure à remplir avec ses filles.
	 */
	protected void getChilds(Structure structure) {
		
		// Récupération de la clef de la structure.
		String key = structure.getKey();
		
		// Si la clef est nulle, on ne peut pas continuer.
		if (key == null) {
			return;
		}
		
		// Récupération de toutes les clefs des structures filles.
		List<String> childrenKeys
				= this.getDataAccess().getStructureChilds(key);
		
		// Pour chaque fille, on remplit ses informations basiques (nom court,
		// nom long, membres, ...) et on ré-appelle cette méthode pour
		// construire sa filiation.
		for (String childKey : childrenKeys) {
			Structure child = this.getStructureByKey(childKey);
			child.setFather(structure);
			this.getChilds(child);
		}
	}
	
	/**
	 * Cette méthode remplit tous les membres d'une structure.
	 * @param structure La structure dont on veut remplir les membres.
	 */
	protected void fillMembres(Structure structure) {
		// Récupération de la clef de la structure.
		String key = structure.getKey();
		ArrayList<String> membresKeys = null;
		
		// Si la clef est null, on ne peut rien faire.
		if (key == null) {
			return;
		}
		
		// Récupération des clefs des membres de cette structure.
		membresKeys = this.getDataAccess().getMembers(key);
		
		// Pour chaque clef membre, on construit le membre et on l'ajoute à la
		// structure.
		for (String memberKey : membresKeys) {
			Membre membre = this.buildMembre(memberKey);
			structure.addMembre(membre);
		}
	}	
	
	/**
	 * Cette méthode remplit tous les assistants leader d'une structure.
	 * @param structure La structure dont on veut remplir les assistants leader.
	 */
	protected void fillAssistants(Structure structure) {
		// Récupération de la clef de la structure.
		String structureKey = structure.getKey();
		ArrayList<String> assistantsKeys = null;
		ArrayList<String> assistantsTitles = null;
		
		// Si la clef est null, on ne peut rien faire.
		if (structureKey == null) {
			return;
		}
		
		// Récupération des clefs et des titres des assistants de cette
		// structure.
		assistantsKeys = this.getDataAccess().getAsistantLeaders(structureKey);
		assistantsTitles =
			this.getDataAccess().getAsistantLeadersTitle(structureKey);
		
		// Pour chaque clef assistant, on construit le membre correspondant et
		// on l'ajoute à la structure.
		for (int i = 0; i < assistantsKeys.size(); i++) {
			Membre assistant = this.buildMembre(assistantsKeys.get(i));
			assistant.setAssistantLeader(true);
			
			try {
				assistant.setTitre(assistantsTitles.get(i));
			} catch (IndexOutOfBoundsException e) {
				// Rien.
			}
			structure.addMembre(assistant);
		}
	}
	
	/**
	 * Cette méthode renseigne le leader d'une structure.
	 * @param structure La structure dont on veut ajouter le leader.
	 */
	protected void fillLeader(Structure structure) {
		// Récupération de la clef de la structure.
		String structureKey = structure.getKey();
		
		// Si la clef est null, on ne peut rien faire.
		if (structureKey == null) {
			return;
		}
		
		// Récupération de la clef du leader de la structure.
		String leaderKey = this.getDataAccess().getLeader(structureKey);
		
		// Si la clef du leader est null, on ne peut rien faire.
		if (leaderKey == null) {
			return;
		}
		
		// Construction du leader et ajout dans la structure.
		Membre leader = this.buildMembre(leaderKey);
		leader.setTitre(this.getDataAccess().getLeaderTitle(structureKey));
		leader.setLeader(true);
		leader.setStructure(structure);
	}
	
	/**
	 * Cette méthode renseigne le contact d'une structure.
	 * @param structure La structure dont on veut ajouter le contact.
	 */
	protected void fillContact(Structure structure) {
		// Récupération de la clef de la structure.
		String key = structure.getKey();
		
		// Si la clef est null, on ne peut rien faire.
		if (key == null) {
			return;
		}
		
		// Récupération de la clef du contact de la structure.
		String contactKey = this.getDataAccess().getContact(key);
		
		// Si la clef du contact est null, on ne peut rien faire.
		if (contactKey == null) {
			return;
		}
		
		// Construction du contact et ajout dans la structure.
		Membre contact = this.buildMembre(contactKey);
		contact.setContact(true);
		contact.setStructure(structure);
	}
	
	
	/**
	 * Construit un objet membre à partir de sa clef.
	 * @param key Une clef de membre.
	 * @return Un objet membre construit. Il n'est pas rattaché à une structure,
	 * et on ne sait pas si il est leader, contact ou assistant.
	 */
	protected Membre buildMembre(String key) {
		Membre membre = new Membre();
		membre.setKey(key);
		membre.setName(this.getDataAccess().getName(key));
		membre.setPhone(this.getDataAccess().getPhone(key));
		membre.setOffice(this.getDataAccess().getOffice(key));
		membre.setFunction(this.getDataAccess().getFunction(key));
		return membre;
	}
	
	/**
	 * Construit un objet membre à partir de sa clef. La structure associée au
	 * membre est renseignée, mais seulement avec sa clef et ses noms court et
	 * long.
	 * @param key Une clef de membre.
	 * @param structureKey Une clef de strcuture.
	 * @return Un objet membre construit.
	 */
	protected Membre buildMembre(String key, String structureKey) {
		Membre membre = this.buildMembre(key);
		
		Structure structure = new Structure();
		structure.setKey(structureKey);
		structure.setShortName(
				this.getDataAccess().getShortName(structureKey));
		structure.setCompleteName(
				this.getDataAccess().getCompleteName(structureKey));
		
		membre.setStructure(structure);
		
		return membre;
	}
	
	/**
	 * Construit un membre leader en précisant le nom de sa structure.
	 * @param key La clef d'un membre.
	 * @param structureKey La clef d'une structure.
	 * @return Un membre leader.
	 */
	protected Membre buildLeader(String key, String structureKey) {
		Membre membre = this.buildMembre(key, structureKey);
		membre.setTitre(this.getDataAccess().getLeaderTitle(structureKey));
		membre.setLeader(true);
		return membre;
	}
	
	/**
	 * Construit un membre assistant leader en précisant le nom de sa structure.
	 * @param key La clef d'un membre.
	 * @param structureKey La clef d'une structure.
	 * @return Un membre assistant leader.
	 */
	protected Membre buildAssistantLeader(String key, String structureKey) {
		Membre membre = this.buildMembre(key, structureKey);
		String membreTitle = null;
		
		int index = this.getDataAccess().getAsistantLeaders(structureKey)
			.indexOf(key);
		
		try {
			membreTitle = this.getDataAccess()
				.getAsistantLeadersTitle(structureKey).get(index);
		} catch (IndexOutOfBoundsException e) {
			// Rien.
		}
		
		membre.setTitre(membreTitle);
		membre.setAssistantLeader(true);
		
		return membre;
	}

	protected List<Structure> getStructures(int level) {
		List<Structure> structures = new LinkedList<Structure>();

		ArrayList<String> keys = getDataAccess().getStructures(level);

		for (String key : keys) {
			Structure s = new Structure();
			s.setKey(key);

			s.setShortName(getDataAccess().getShortName(key));
			s.setCompleteName(getDataAccess().getCompleteName(key));

			String clefLeader = getDataAccess().getLeader(key);
			Membre leader = this.buildMembre(clefLeader);
			leader.setLeader(true);
			s.addMembre(leader);

			String clefContact = getDataAccess().getContact(key);
			Membre contact = this.buildMembre(clefContact);
			contact.setContact(true);
			s.addMembre(contact);

			ArrayList<String> clefsAssistantsLeaders = getDataAccess()
					.getAsistantLeaders(key);
			for (String clefsAssistantsLeader : clefsAssistantsLeaders) {
				Membre assistant = new Membre();
				assistant.setKey(clefsAssistantsLeader);
				assistant.setName(getDataAccess()
						.getName(clefsAssistantsLeader));
				assistant.setPhone(getDataAccess().getPhone(
						clefsAssistantsLeader));
				assistant.setOffice(getDataAccess().getOffice(
						clefsAssistantsLeader));
				assistant.setFunction(getDataAccess().getFunction(
						clefsAssistantsLeader));

				s.addMembre(assistant);
			}

			ArrayList<String> clefsMembres = getDataAccess().getMembers(key);
			for (String clefMembre : clefsMembres) {
				Membre membre = new Membre();
				membre.setKey(clefMembre);
				membre.setName(getDataAccess().getName(clefMembre));
				membre.setPhone(getDataAccess().getPhone(clefMembre));
				membre.setOffice(getDataAccess().getOffice(clefMembre));
				membre.setFunction(getDataAccess().getFunction(clefMembre));

				s.addMembre(membre);
			}

			structures.add(s);
		}

		return structures;
	}

	/**
	 * Renvoie une liste de membres. La recherche est faite à la fois sur le
	 * nom, la fonction et la structure.
	 */
	@Override
	public List<Membre> getMembres(String paramName, String paramFunction,
			String paramStructure) {
		
		String name = this.normalizeParam(paramName);
		String function = this.normalizeParam(paramFunction);
		String structure = this.normalizeParam(paramStructure);
		
		if (name.isEmpty() && function.isEmpty() && structure.isEmpty()) {
			return this.getMembresByName("");
		}
		
		List<Membre> list1 = null;
		List<Membre> list2 = null;
		List<Membre> list3 = null;
		
		if (!name.isEmpty()) {
			list1 = this.getMembresByName(name);
		}
		
		if (!function.isEmpty()) {
			list2 = this.getMembresByFunction(function);
		}
		
		if (!structure.isEmpty()) {
			list3 = this.getMembresByStructure(structure);
		}
		
		@SuppressWarnings("unchecked")
		List<Membre> res = 
			AnnuaireUtil.getInstance().getCommonMembers(list1, list2, list3);
		
		return res;
	}
	
	
	/**
	 * Renvoie une liste de membres en recherchant par leur nom. La recherche ne
	 * tient pas compte de la casse. Si le nom est une chaîne vide ou null, tous
	 * les membres sont renvoyés. Exemple: soit deux membres "TOTO" et "TATA".
	 * Si on recherche "toT", on aura "TOTO". Si on recherche "t" ou "" ou null,
	 * on aura "TOTO" et "TATA".
	 * @param name Le nom (ou la partie du nom) du membre à rechercher.
	 * @return Une liste de membres.
	 */
	@Override
	public List<Membre> getMembresByName(String name) {
		// La liste des membres à renvoyer.
		List<Membre> membres = new LinkedList<Membre>();

		// On remet en forme le nom.
		String nom = this.normalizeParam(name);

		// On cherche toutes les structures de niveau 1.
		int niveauStructure = 1;
		ArrayList<String> clefsStructures = new ArrayList<String>(16);
		clefsStructures = getDataAccess().getStructures(niveauStructure);

		// Tant qu'on trouve des structures:
		while (!clefsStructures.isEmpty()) {
			// Pour chaque structure:
			for (String clefStructure : clefsStructures) {
				
				// Recherche du leader.
				String leaderKey =
					this.getDataAccess().getLeader(clefStructure);
				String nomLeader = this.getDataAccess().getName(leaderKey);
				
				this.addLeader(nomLeader, nom, membres, leaderKey,
						clefStructure);
				
				// Recherche du contact.
				String contactKey =
					this.getDataAccess().getContact(clefStructure);
				String nomContact = this.getDataAccess().getName(contactKey);
				if (nomContact != null && nomContact.toLowerCase().contains(nom)) {
					membres.add(this.buildMembre(contactKey, clefStructure));
				}
				
				// Recherche des assistants leader.
				List<String> assistantsKeys =
					this.getDataAccess().getAsistantLeaders(clefStructure);
				for (String assistantKey : assistantsKeys) {
					Membre assistant = this.buildAssistantLeader(assistantKey,
							clefStructure);
					if (assistant != null && assistant.getName() != null  && assistant.getName().toLowerCase().contains(nom)) {
						membres.add(assistant);
					}
				}
				
				// On recherche toutes les clefs des membres de cette structure.
				ArrayList<String> membresStructure = getDataAccess()
						.getMembers(clefStructure);

				// On crée un structure et on renseigne sa clef et ses noms.
				Structure structure = new Structure();
				structure.setKey(clefStructure);
				structure.setCompleteName(getDataAccess().getCompleteName(
						clefStructure));
				structure.setShortName(getDataAccess().getShortName(
						clefStructure));

				// Pour chaque clef membre:
				for (String clefMembre : membresStructure) {
					
					this.fillMembre(nom, clefMembre, structure, membres);
					
				}

			}

			// On passe au niveau de structure suivant.
			niveauStructure++;

			// On récupère les clefs des structures de ce nouveau niveau.
			clefsStructures = getDataAccess().getStructures(niveauStructure);
		}
		
		// Fin.
		return membres;
	}
	
	private void addLeader(String nomLeader, String nom, List<Membre> membres,
			String leaderKey, String clefStructure) {
		if (nomLeader != null && nomLeader.toLowerCase().contains(nom)) {
			membres.add(this.buildLeader(leaderKey, clefStructure));
		}
	}
	
	private void fillMembre(String nom, String clefMembre, Structure structure,
			List<Membre> membres) {
		String nomMembre = getDataAccess().getName(clefMembre)
				.trim();

		// Si ce nom contient le nom recherché, on renseigne tout
		// ses attributs, et on l'ajoute à la liste de membres à
		// renvoyer.
		if (nomMembre.toLowerCase().contains(nom.toLowerCase())) {
			Membre membre = new Membre();
			membre.setKey(clefMembre);
			membre.setName(nomMembre);
			membre.setPhone(getDataAccess().getPhone(clefMembre));
			membre.setOffice(getDataAccess().getOffice(clefMembre));
			membre.setFunction(getDataAccess().getFunction(
					clefMembre));
			membre.setStructure(structure);
		
			boolean found = false;
			for (Membre m : membres) {
				if (m.getKey().equals(membre.getKey())) {
					found = true;
					break;
				}
			}
			
			if (!found) {
				membres.add(membre);
			}
		}
	}

         /**
	 * Renvoie une liste de membres en recherchant par leur function. La recherche ne
	 * tient pas compte de la casse. Si la fonction est une chaîne vide ou null, tous
	 * les membres sont renvoyés.
	 * @param function La fonction (ou la partie de la fonction) du membre à rechercher.
	 * @return Une liste de membres.
	 */

    @Override
	public List<Membre> getMembresByFunction(String function) {
		// La liste des membres à renvoyer.
		List<Membre> membres = new LinkedList<Membre>();

		// On remet en forme la function.
		String lib_function = function;
		if (lib_function == null)
			lib_function = "";
		else
			lib_function = function.trim();

		// On cherche toutes les structures de niveau 1.
		int niveauStructure = 1;
		ArrayList<String> clefsStructures = new ArrayList<String>(16);
		clefsStructures = getDataAccess().getStructures(niveauStructure);

		// Tant qu'on trouve des structures:
		while (!clefsStructures.isEmpty()) {
			// Pour chaque structure:
			for (String clefStructure : clefsStructures) {

				// On recherche toutes les clefs des membres de cette structure.
				ArrayList<String> membresStructure = getDataAccess()
						.getMembers(clefStructure);

				// On crée un structure et on renseigne sa clef et ses noms.
				Structure structure = new Structure();
				structure.setKey(clefStructure);
				structure.setCompleteName(getDataAccess().getCompleteName(
						clefStructure));
				structure.setShortName(getDataAccess().getShortName(
						clefStructure));

				// Pour chaque clef membre:
				for (String clefMembre : membresStructure) {
					
					this.fillFunction(clefMembre, lib_function, structure,
							membres);

				}

			}

			// On passe au niveau de structure suivant.
			niveauStructure++;

			// On récupère les clefs des structures de ce nouveau niveau.
			clefsStructures = getDataAccess().getStructures(niveauStructure);
		}

		// Fin.
		return membres;
	}

    private void fillFunction(String clefMembre, String lib_function,
    		Structure structure, List<Membre> membres) {
    	String functionMembre = getDataAccess().getFunction(
				clefMembre).trim();

		// Si cette fonction contient la fonction recherché, on
		// renseigne tout
		// ses attributs, et on l'ajoute à la liste de membres à
		// renvoyer.
		if (functionMembre.toLowerCase().contains(
				lib_function.toLowerCase())) {

			Membre membre = new Membre();
			membre.setKey(clefMembre);
			membre.setName(getDataAccess().getName(clefMembre));
			membre.setPhone(getDataAccess().getPhone(clefMembre));
			membre.setOffice(getDataAccess().getOffice(clefMembre));
			membre.setFunction(getDataAccess().getFunction(
					clefMembre));
			membre.setStructure(structure);
			boolean found = false;
			for (Membre m : membres) {
				if (m.getKey().equals(membre.getKey())) {
					found = true;
					break;
				}
			}

			if (!found) {
				membres.add(membre);
			}

		}
    }
  
    @Override
    public List<Membre> getMembresByNameWithFonction(String name,List<Membre> LaList) {
		// La liste des membres à renvoyer.
		List<Membre> membres = new LinkedList<Membre>();

                // La liste des membres à renvoyer.
		List<Membre> membreByFonction = LaList;

		// On remet en forme la function.
		String le_nom = name;
		if (le_nom == null)
			le_nom = "";
		else
			le_nom = name.trim();

			for (Membre leMembre : membreByFonction) {
                            if(leMembre.getName().toLowerCase().contains(le_nom.toLowerCase())){
                               membres.add(leMembre);
                           }

                        }
		// Fin.
		return membres;
	}

         /**
	 * Renvoie une liste de membres en recherchant par leur structure. La recherche ne
	 * tient pas compte de la casse. Si la structure est une chaîne vide ou null, tous
	 * les membres sont renvoyés.
	 * @param laStructure La structure (ou la partie du structure) du membre à rechercher.
	 * @return Une liste de membres.
	 */

    @Override
	public List<Membre> getMembresByStructure(String laStructure) {
		// La liste des membres à renvoyer.
		List<Membre> membres = new LinkedList<Membre>();
		List<Structure> structures = new LinkedList<Structure>();
		// List<Structure> structuresFilles = new LinkedList<Structure>();

		// On remet en forme le nom.
		String lib_structure = laStructure;
		if (lib_structure == null)
			lib_structure = "";
		else
			lib_structure = laStructure.trim();
		structures = getCompleteStructures();

		for (Structure couranteStruct : structures) {
			this.fillStructure(couranteStruct, lib_structure, membres);
		}
		// Fin.
		return membres;
	}

	private void fillStructure(Structure couranteStruct, String lib_structure,
			List<Membre> membres) {
		if (couranteStruct.getCompleteName().toLowerCase()
				.contains(lib_structure.toLowerCase())) {
			String clefStructure = couranteStruct.getKey();
			// Recherche du leader.
			String leaderKey = this.getDataAccess().getLeader(clefStructure);
			this.getDataAccess().getName(leaderKey);
			membres.add(this.buildLeader(leaderKey, clefStructure));

			// Recherche du contact.
			String contactKey = this.getDataAccess().getContact(clefStructure);
			this.getDataAccess().getName(contactKey);
			membres.add(this.buildMembre(contactKey, clefStructure));

			// Recherche des assistants leader.
			List<String> assistantsKeys = this.getDataAccess()
					.getAsistantLeaders(clefStructure);
			for (String assistantKey : assistantsKeys) {
				Membre assistant = this.buildAssistantLeader(assistantKey,
						clefStructure);
				membres.add(assistant);
			}
			List<Membre> membresStructure = couranteStruct.getMembres();
			// Pour chaque membre:
			for (Membre membre : membresStructure) {
				boolean found = false;
				for (Membre m : membres) {
					if (m.getKey().equals(membre.getKey())) {
						found = true;
						break;
					}
				}

				if (!found) {
					membres.add(membre);
				}
			}
		}
	}
    
    /**
	 * Renvoie la liste de toutes les structures de l'annuaire. La liste
	 * renvoyée contient les structures du premier niveau. Pour accéder aux
	 * niveaux inférieurs, il faut appeler la méthode getChilds() d'une
	 * structure.
	 * @return La liste des structures sur 3 niveaux.
	 */
	@Override
	public List<Structure> getCompleteStructures() {

		// La liste des structures que l'on va renvoyer.
		List<Structure> structures = new LinkedList<Structure>();
                // La liste des structures que l'on va renvoyer.
		List<Structure> structuresFilles = new LinkedList<Structure>();
                List<Structure> structuresSousFilles = new LinkedList<Structure>();
               
		structures = getAllStructures();
		for(Structure couranteStruct:structures)
                {
                        structuresFilles.addAll(couranteStruct.getChilds());
                        for(Structure SousFilles : structuresFilles)
                        {
                            structuresSousFilles.addAll(SousFilles.getChilds());
                        }
                        structuresFilles.addAll(structuresSousFilles);
                        structuresFilles.add(couranteStruct);
                }

		// Fin.
		return structuresFilles;
	}

	/**
	 * Trimme et met en minuscule un paramètre de recherche. Si le paramètre est
	 * null, une chaîne vide est renvoyée.
	 * @param param Un paramètre de recherche.
	 * @return Le paramètre bien mis en forme.
	 */
	private String normalizeParam(String param) {
		
		if (param == null) {
			return "";
		}
		
		String res = param.trim();
		res = res.toLowerCase();
		
		return res;
	}
	
	/**
	 * Acesseur en lecture sur l'attribut 'dataAccess'.
	 * @return L'interface d'accès aux données.
	 */
	public DataAccess getDataAccess() {
		return DataAccessFactory.getInstance().getDataAccess();
	}

}
